/*
 * Copyright 2005-2007 Noelios Consulting.
 * 
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the "License"). You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at
 * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL HEADER in each file and
 * include the License file at http://www.opensource.org/licenses/cddl1.txt If
 * applicable, add the following below this CDDL HEADER, with the fields
 * enclosed by brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 */

package org.restlet.resource;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.xpath.XPathConstants;

import org.restlet.data.MediaType;
import org.restlet.util.NodeSet;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Representation based on an XML document. It knows how to evaluate XPath
 * expressions and how to manage a namespace context.
 * 
 * @author Jerome Louvel (contact@noelios.com)
 */
public abstract class XmlRepresentation extends OutputRepresentation implements
		NamespaceContext {
	/** Internal map of namespaces. */
	private Map<String, String> namespaces;

	/** Indicates if processing is namespace aware. */
	private boolean namespaceAware;

	/**
	 * Constructor.
	 * 
	 * @param mediaType
	 *            The representation's mediaType.
	 */
	public XmlRepresentation(MediaType mediaType) {
		super(mediaType);
		this.namespaces = null;
		this.namespaceAware = false;
	}

	/**
	 * Constructor.
	 * 
	 * @param mediaType
	 *            The representation's mediaType.
	 * @param expectedSize
	 *            The expected input stream size.
	 */
	public XmlRepresentation(MediaType mediaType, long expectedSize) {
		super(mediaType, expectedSize);
		this.namespaces = null;
		this.namespaceAware = false;
	}

	/**
	 * Evaluates an XPath expression and returns the result as in the given
	 * return type.
	 * 
	 * @param returnType
	 *            The qualified name of the return type.
	 * @return The evaluation result.
	 * @see javax.xml.xpath.XPathException
	 * @see javax.xml.xpath.XPathConstants
	 */
	public abstract Object evaluate(String expression, QName returnType)
			throws Exception;

	/**
	 * Evaluates an XPath expression as a boolean. If the evaluation fails, null
	 * will be returned.
	 * 
	 * @return The evaluation result.
	 */
	public Boolean getBoolean(String expression) {
		return (Boolean) internalEval(expression, XPathConstants.BOOLEAN);
	}

	/**
	 * Returns the map of namespaces.
	 * 
	 * @return The map of namespaces.
	 */
	private Map<String, String> getNamespaces() {
		if (this.namespaces == null)
			this.namespaces = new HashMap<String, String>();
		return this.namespaces;
	}

	/**
	 * {@inheritDoc javax.xml.namespace.NamespaceContext#getNamespaceURI(java.lang.String}
	 */
	public String getNamespaceURI(String prefix) {
		return this.namespaces.get(prefix);
	}

	/**
	 * Evaluates an XPath expression as a DOM Node. If the evaluation fails,
	 * null will be returned.
	 * 
	 * @return The evaluation result.
	 */
	public Node getNode(String expression) {
		return (Node) internalEval(expression, XPathConstants.NODE);
	}

	/**
	 * Evaluates an XPath expression as a DOM NodeList. If the evaluation fails,
	 * null will be returned.
	 * 
	 * @return The evaluation result.
	 */
	public NodeSet getNodes(String expression) {
		NodeList nodes = (NodeList) internalEval(expression,
				XPathConstants.NODESET);
		return (nodes == null) ? null : new NodeSet(nodes);
	}

	/**
	 * Evaluates an XPath expression as a number. If the evaluation fails, null
	 * will be returned.
	 * 
	 * @return The evaluation result.
	 */
	public Double getNumber(String expression) {
		return (Double) internalEval(expression, XPathConstants.NUMBER);
	}

	/**
	 * {@inheritDoc javax.xml.namespace.NamespaceContext#getPrefix(java.lang.String}
	 */
	public String getPrefix(String namespaceURI) {
		String result = null;

		for (Entry<String, String> entry : getNamespaces().entrySet()) {
			if ((result == null) && entry.getValue().equals(namespaceURI))
				result = entry.getKey();
		}

		return result;
	}

	/**
	 * {@inheritDoc javax.xml.namespace.NamespaceContext#getPrefixes(java.lang.String}
	 */
	public Iterator<String> getPrefixes(String namespaceURI) {
		List<String> result = new ArrayList<String>();

		for (Entry<String, String> entry : getNamespaces().entrySet()) {
			if (entry.getValue().equals(namespaceURI))
				result.add(entry.getKey());
		}

		return Collections.unmodifiableList(result).iterator();
	}

	/**
	 * Evaluates an XPath expression as a string.
	 * 
	 * @return The evaluation result.
	 */
	public String getText(String expression) {
		return (String) internalEval(expression, XPathConstants.STRING);
	}

	/**
	 * Evaluates an XPath expression and returns the result as in the given
	 * return type.
	 * 
	 * @param returnType
	 *            The qualified name of the return type.
	 * @return The evaluation result.
	 */
	private Object internalEval(String expression, QName returnType) {
		try {
			return evaluate(expression, returnType);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Indicates if processing is namespace aware.
	 * 
	 * @return True if processing is namespace aware.
	 */
	public boolean isNamespaceAware() {
		return this.namespaceAware;
	}

	/**
	 * Puts a new mapping between a prefix and a namespace URI.
	 * 
	 * @param prefix
	 *            The namespace prefix.
	 * @param namespaceURI
	 *            The namespace URI.
	 */
	public void putNamespace(String prefix, String namespaceURI) {
		getNamespaces().put(prefix, namespaceURI);
	}

	/**
	 * Indicates if processing is namespace aware.
	 * 
	 * @param namespaceAware
	 *            Indicates if processing is namespace aware.
	 */
	public void setNamespaceAware(boolean namespaceAware) {
		this.namespaceAware = namespaceAware;
	}

}
